package com.chenjia.proto.converter;

import com.chenjia.proto.converter.annotation.ProtoClass;
import com.chenjia.proto.converter.utils.FieldUtils;
import com.google.protobuf.Message;
import lombok.AllArgsConstructor;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import sun.reflect.generics.reflectiveObjects.WildcardTypeImpl;

import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.lang.reflect.*;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

/**
 * 用于处理、维护 反射过程中的各种状态信息
 */
final class MethodHandleManager {

    // message2pojo = {message,pojo}:{message-setter:pojo-getter}
    /**
     * 保存一对 {message,pojo} 转换的模型，其中 value 代表了所有的 {message.setter:pojo.getter}
     * <p>
     * 这一内容将会延迟构建，为获得较好的体验，建议热机
     */
    private final static ConcurrentMap<Pair<? extends Message, ?>, List<Message2PojoModel>> message2pojoMap = new ConcurrentHashMap<>();

    static List<Message2PojoModel> getMessage2PojoModel(Class<? extends Message> messageClass, Class<?> pojoClass) {
        Pair<? extends Message, ?> key = new Pair<>(messageClass, pojoClass);
        // 其实应该控制 compute 的并发，没必要一起执行 buildMessage2PojoModel 方法，否则可能会影响整体吞吐量
        return message2pojoMap.computeIfAbsent(key, MethodHandleManager::buildMessage2PojoModel);
    }

    /**
     * 解析 Message to Pojo 结构
     */
    private static List<Message2PojoModel> buildMessage2PojoModel(Pair<? extends Message, ?> pair) {
        // pair.key.filed[i].setter -> pair.value.filed[i].getter
        final Class<? extends Message> messageClass = pair.key;
        final Class<?> pojoClass = pair.value;

        // 获得 setter、getter，基于 pojo 类的 field 名拼接出对应方法名，message 类的 filed 名称不正常，而且不包含控制参数
        final List<Field> pojoFields = getClassFields(pojoClass);

        List<Message2PojoModel> list = new ArrayList<>();
        for (Field field : pojoFields) {
            if (Modifier.isStatic(field.getModifiers())) {
                continue;
            }
            // 以此构建出适合于 pojo 与 message 的 setter & getter
            try {
                // 使用出参、入参类型构造 MethodType，再以此基础加上方法名称、方法类构造出预制 MethodHandle。
                // 注意：在使用前还需要 bindTo 目标对象实例，这样才能构造出可用的 MethodHandle。
                final MethodHandle messageGetMethodHandle;
                final Method messageGetMethod;
                {
                    final String protobufGetterName = FieldUtils.createProtobufGetterName(field.getName(), field.getType());
                    messageGetMethod = messageClass.getMethod(protobufGetterName);
                    final Class<?> returnType = messageGetMethod.getReturnType();
                    // 入参为空
                    // protobufGetterName
                    final MethodType methodType = MethodType.methodType(returnType);
                    messageGetMethodHandle = MethodHandles.lookup().findVirtual(messageClass, protobufGetterName, methodType);
                }
                final MethodHandle pojoSetMethodHandle;
                final Method pojoSetMethod;
                {
                    final String pojoSetterName = FieldUtils.createDomainSetterName(field.getName());
                    pojoSetMethod = pojoClass.getMethod(pojoSetterName, field.getType());
                    final Class<?> parameterType = pojoSetMethod.getParameterTypes()[0];
                    final MethodType methodType = MethodType.methodType(void.class, parameterType);
                    pojoSetMethodHandle = MethodHandles.lookup().findVirtual(pojoClass, pojoSetterName, methodType);
                }
                list.add(new Message2PojoModel(messageGetMethodHandle, messageGetMethod, pojoSetMethodHandle, pojoSetMethod));
            } catch (NoSuchMethodException | IllegalAccessException e) {
                // continue
                e.printStackTrace();
            }
        }
        return list;
    }

    private static List<Field> getClassFields(final Class clazz) {
        List<Field> fields = new ArrayList<>(Arrays.asList(clazz.getDeclaredFields()));
        Class superClazz = clazz.getSuperclass();
        // 这个标记需要修改目标类 "&& superClazz.isAnnotationPresent(ProtoSuper.class)"
        if (superClazz != null) {
            fields.addAll(getClassFields(superClazz));
        }
        return fields;
    }

    final static ConcurrentMap<Method, Class> setterParamClassMap = new ConcurrentHashMap<>();

    static Class buildSetterParamClass(final Method setter) {
        final Class<?> parameterType = setter.getParameterTypes()[0];
        if (!parameterType.isAnnotationPresent(ProtoClass.class)) {
            throw new ProtoConverterException("内联pojo没有 @ProtoClass 标识, parameterType=" + parameterType);
        }
        return parameterType;
    }

    static Class buildSetterCollectionParamClass(final Method setter) {
        if (!Collection.class.isAssignableFrom(setter.getParameterTypes()[0])) {
            throw new ProtoConverterException("参数类型不是集合");
        }
        final Parameter parameter = setter.getParameters()[0];
        final ParameterizedType parameterizedType = (ParameterizedType) parameter.getParameterizedType();
        final Class<?> actualTypeArgument = (Class<?>) parameterizedType.getActualTypeArguments()[0];
        if (actualTypeArgument.isAnnotationPresent(ProtoClass.class)) {
            return actualTypeArgument;
        }
        return null;
    }

    /**
     * 对于 `void setXXX(Iterable<? extends AAA> value)`，这里将会获取到 `AAA.class`
     */
    static Class buildMessageSetterCollectionParamClass(final Method setter) {
        if (!Iterable.class.isAssignableFrom(setter.getParameterTypes()[0])) {
            throw new ProtoConverterException("参数类型不是集合");
        }
        final Parameter parameter = setter.getParameters()[0];
        final ParameterizedType parameterizedType = (ParameterizedType) parameter.getParameterizedType();
        final Type actualTypeArgument = parameterizedType.getActualTypeArguments()[0];
        final String typeName = ((WildcardTypeImpl) actualTypeArgument).getUpperBounds()[0].getTypeName();
        try {
            final Class<?> elementType = Class.forName(typeName);
            if (Message.class.isAssignableFrom(elementType)) {
                return elementType;
            }
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        return null;
    }

    static Class buildSetterMapParamClass(final Method setter) {
        if (!Map.class.isAssignableFrom(setter.getParameterTypes()[0])) {
            throw new ProtoConverterException("参数类型不是 Map");
        }
        final Parameter parameter = setter.getParameters()[0];
        final ParameterizedType parameterizedType = (ParameterizedType) parameter.getParameterizedType();
        // 取第二个泛型实参
        final Class<?> actualTypeArgument = (Class<?>) parameterizedType.getActualTypeArguments()[1];
        if (actualTypeArgument.isAnnotationPresent(ProtoClass.class)) {
            // 基本类型（可直接使用的）直接返回空
            return null;
        }
        return actualTypeArgument;
    }

    private final static ConcurrentMap<Method, Class> messageGetterReturnTypeMap = new ConcurrentHashMap<>();

    static Class getMessageGetterReturnType(final Method getter) {
        return messageGetterReturnTypeMap.computeIfAbsent(getter, Method::getReturnType);
    }


    // ------------------------------

    /**
     * @see this#message2pojoMap
     */
    private final static ConcurrentMap<Pair<?, ? extends Message.Builder>, List<Pojo2messageModel>> pojo2messageMap = new ConcurrentHashMap<>();

    static List<Pojo2messageModel> getPojo2messageModel(Class<?> pojoClass, Class<? extends Message.Builder> messageBuilderClass) {
        Pair<?, ? extends Message.Builder> key = new Pair<>(pojoClass, messageBuilderClass);
        return pojo2messageMap.computeIfAbsent(key, MethodHandleManager::buildPojo2messageModel);
    }

    private static List<Pojo2messageModel> buildPojo2messageModel(Pair<?, ? extends Message.Builder> pair) {
//        log.debug("反射解析 pair={} 信息", pair);
        final Class<?> pojoClass = pair.key;
        final Class<? extends Message.Builder> messageBuilderClass = pair.value;

        final List<Field> pojoFields = getClassFields(pojoClass);
        List<Pojo2messageModel> list = new ArrayList<>();
        for (Field field : pojoFields) {
            if (Modifier.isStatic(field.getModifiers())) {
                continue;
            }
            try {
                final Class<?> fieldType = field.getType();
                String pojoGetName = FieldUtils.createDomainGetterName(fieldType, field.getName());
                final Method pojoGetMethod = pojoClass.getMethod(pojoGetName);
                final MethodHandle pojoGetMethodHandle = MethodHandles.lookup().findVirtual(pojoClass, pojoGetName, MethodType.methodType(fieldType));

                // 如果是 List，需要用 Iterable 代替
                Class messageFieldType;
                if (Collection.class.isAssignableFrom(fieldType)) {
                    messageFieldType = Iterable.class;
                } else if (fieldType.isAnnotationPresent(ProtoClass.class)) {
                    // 需要获取message类型
                    final Method method = messageBuilderClass.getMethod(pojoGetName);
                    messageFieldType = method.getReturnType();
                } else {
                    messageFieldType = fieldType;
                }
                final String messageSetName = FieldUtils.createProtobufSetterName(fieldType, field.getName());
                final Method messageBuilderSetMethod = messageBuilderClass.getMethod(messageSetName, messageFieldType);
                final MethodHandle messageBuilderSetMethodHandle = MethodHandles.lookup()
                        .findVirtual(messageBuilderClass, messageSetName, MethodType.methodType(messageBuilderClass, messageFieldType));
                list.add(new Pojo2messageModel(pojoGetMethodHandle, pojoGetMethod, messageBuilderSetMethodHandle, messageBuilderSetMethod));
            } catch (NoSuchMethodException | IllegalAccessException e) {
                // continue
                e.printStackTrace();
            }
        }
        return list;
    }

    private final static ConcurrentMap<Method, Class> pojoGetterReturnTypeMap = new ConcurrentHashMap<>();

    static Class getPojoGetterReturnType(final Method getter) {
        return pojoGetterReturnTypeMap.computeIfAbsent(getter, Method::getReturnType);
    }


    @AllArgsConstructor
    public static class Message2PojoModel {
        public final MethodHandle messageGetMethodHandle;
        public final Method messageGetMethod;
        public final MethodHandle pojoSetMethodHandle;
        public final Method pojoSetMethod;
    }

    @AllArgsConstructor
    public static class Pojo2messageModel {
        public final MethodHandle pojoGetMethodHandle;
        public final Method pojoGetMethod;
        public final MethodHandle messageBuilderSetMethodHandle;
        public final Method messageBuilderSetMethod;
    }

    /**
     * 主要是 setter
     */
    @ToString
    @EqualsAndHashCode
    public static class FieldWriter {
        public Method setter;
        /**
         * 仅供字段声明为 {@link com.google.protobuf.Any} 时，作为对应转换目标，但这并不是最终 Pojo 结果
         */
        public Class<? extends Message> anyTarget;
    }

    /**
     * 主要是 getter
     */
    @ToString
    @EqualsAndHashCode
    public static class FieldReader {
        public Method getter;
    }
}
